package test.cards;

import basemod.BaseMod;
import basemod.abstracts.CustomCard;
import basemod.abstracts.DynamicVariable;
import com.badlogic.gdx.graphics.Color;
import com.megacrit.cardcrawl.actions.AbstractGameAction;
import com.megacrit.cardcrawl.actions.common.*;
import com.megacrit.cardcrawl.cards.AbstractCard;
import com.megacrit.cardcrawl.cards.DamageInfo;
import com.megacrit.cardcrawl.core.CardCrawlGame;
import com.megacrit.cardcrawl.core.Settings;
import com.megacrit.cardcrawl.dungeons.AbstractDungeon;
import com.megacrit.cardcrawl.localization.CardStrings;
import com.megacrit.cardcrawl.monsters.AbstractMonster;
import com.megacrit.cardcrawl.powers.AbstractPower;
import mutsumimod.MutsumiMod;
import mutsumimod.util.CardStats;
import mutsumimod.util.TriFunction;
import test.patches.TagsPatch;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import static mutsumimod.util.GeneralUtils.removePrefix;
import static mutsumimod.util.TextureLoader.getCardTextureString;


public abstract class BaseCaptainCard extends CustomCard {

    public boolean getHasDoll() {
        return TagsPatch.CardFieldPatch.hasDoll.get(this);
    }

    public void setHasDoll(boolean isDoll) {
        TagsPatch.CardFieldPatch.hasDoll.set(this, isDoll);
    }

    public boolean getIsDollWaken(){
        return getHasDoll()&&TagsPatch.CardFieldPatch.isDollWaken.get(this);
    }

    public void setIsDollWaken(boolean isDollWaken) {
        TagsPatch.CardFieldPatch.isDollWaken.set(this, isDollWaken);
    }

    public boolean getHasAnchor() {
        return TagsPatch.CardFieldPatch.hasAnchor.get(this);
    }

    public void setHasAnchor(boolean isAnchor) {
        TagsPatch.CardFieldPatch.hasAnchor.set(this, isAnchor);
    }

    public void triggerOnActivate(){
        superFlash();
        if(getHasDoll()){
            setIsDollWaken(true);
        }
    }

    @Override
    public void triggerOnGlowCheck(){
        if(getHasDoll()){
            if(getIsDollWaken()){
                glowColor = Color.GOLD.cpy();
                return;
            }
        }
        super.triggerOnGlowCheck();
    }

//    public void onAddToHand(){
//
//    }
//
//    public void onRemoveFromHand(){
//
//    }
//
//    public void triggerOnRightClick(){
//        addToBot(new LoseEnergyAction(1));
//    }
//
//    public void setLightHouse(boolean hasLightHouse) {
//        TagsPatch.CardFieldPatch.hasLightHouse.set(this, hasLightHouse);
//    }
//
//    public void setIsLight(boolean isLight) {
//        TagsPatch.CardFieldPatch.isLight.set(this, isLight);
//    }
//
//    public void setHasPort(boolean hasPort) {
//        TagsPatch.CardFieldPatch.hasPort.set(this, hasPort);
//    }
//
//    public void setSakiInfo(SakiPatch.SakiInfo sakiInfo) {
//        SakiPatch.SakiFieldPatch.sakiInfo.set(this, sakiInfo);
//    }
//
//    public boolean getHasLightHouse() {
//        return TagsPatch.CardFieldPatch.hasLightHouse.get(this);
//    }
//
//    public boolean getIsLight(){
//        return TagsPatch.CardFieldPatch.isLight.get(this);
//    }
//
//    public boolean getHasPort() {
//        return TagsPatch.CardFieldPatch.hasPort.get(this);
//    }
//
//    public SakiPatch.SakiInfo getSakiInfo() {
//        return SakiPatch.SakiFieldPatch.sakiInfo.get(this);
//    }
//
//    public void triggerOnSakiPass(){
//        if(getHasLightHouse()){
//            setIsLight(true);
//            superFlash();
//            triggerOnGlowCheck();
//            playSfx();
//        }
//    }
//
//    public void playSfx() {
//        int roll = MathUtils.random(0, 2);
//        if (roll == 0) {
//            CardCrawlGame.sound.play("BUFF_1");
//        } else if (roll == 1) {
//            CardCrawlGame.sound.play("BUFF_2");
//        } else {
//            CardCrawlGame.sound.play("BUFF_3");
//        }
//    }
//
//    public void triggerOnSakiStartTravel(){
//
//    }
//
//    @Override
//    public void triggerOnGlowCheck() {
//        if(getIsLight()){
//            glowColor = Color.GOLD.cpy();
//        }else{
//            super.triggerOnGlowCheck();
//        }
//    }
//
//    public void setAnchor(boolean isAnchor){
//        TagsPatch.CardFieldPatch.hasAnchor.set(this,isAnchor);
//    }

    public int magicNumberTwo;
    public int magicNumberTwoUpgrade;

    public void gainEnergy(int amount){
        addToBot(new GainEnergyAction(amount));
    }

    public void gainEnergy(){
        addToBot(new GainEnergyAction(magicNumber));
    }

    public void drawCard() {
        addToBot(new DrawCardAction(AbstractDungeon.player, magicNumber));
    }

    public void drawCard(int amount) {
        addToBot(new DrawCardAction(AbstractDungeon.player, amount));
    }

    public void damageAllMonster() {
        damageAllMonster(baseDamage,1,AbstractGameAction.AttackEffect.SLASH_VERTICAL);
    }

    public void damageAllMonster(int baseDamage) {
        damageAllMonster(baseDamage,1,AbstractGameAction.AttackEffect.SLASH_VERTICAL);
    }

    public void damageAllMonster(int baseDamage,int damageTime) {
        damageAllMonster(baseDamage,damageTime,AbstractGameAction.AttackEffect.SLASH_VERTICAL);
    }

    public void damageAllMonster(int baseDamage,int damageTime,AbstractGameAction.AttackEffect effect) {
        for(int i=0;i<damageTime;i++){
            addToBot(new DamageAllEnemiesAction(AbstractDungeon.player, baseDamage, DamageInfo.DamageType.NORMAL , effect));
        }
    }

    public void damageMonster(AbstractMonster m) {
        damageMonster(damage,m,1, AbstractGameAction.AttackEffect.SLASH_VERTICAL);
    }

    public void damageMonster(int damage, AbstractMonster m) {
        damageMonster(damage,m,1, AbstractGameAction.AttackEffect.SLASH_VERTICAL);
    }

    public void damageMonster(AbstractMonster m, int damageTime) {
        damageMonster(damage,m,damageTime, AbstractGameAction.AttackEffect.SLASH_VERTICAL);
    }

    public void damageMonster(int damage, AbstractMonster m, int damageTime) {
        damageMonster(damage,m,damageTime, AbstractGameAction.AttackEffect.SLASH_VERTICAL);
    }

    public void damageMonster(AbstractMonster m, AbstractGameAction.AttackEffect effect) {
        damageMonster(damage,m,1,effect);
    }


    public void damageMonster(int damage, AbstractMonster m, int damageTime, AbstractGameAction.AttackEffect effect) {
        for(int i=0;i<damageTime;i++){
            addToBot(new DamageAction(m, new DamageInfo(AbstractDungeon.player
                    , damage, DamageInfo.DamageType.NORMAL), effect));
        }
    }

    public void gainBlock(int block){
        addToBot(new GainBlockAction(AbstractDungeon.player, block));
    }

    public void gainBlock(){
        addToBot(new GainBlockAction(AbstractDungeon.player, block));
    }

    public void reducePower(String powerID, int amount){
        addToBot(new ReducePowerAction(AbstractDungeon.player, AbstractDungeon.player, powerID, amount));
    }

    public void applyPowerSelf(AbstractPower power,int magicNumber){
        if(magicNumber==0){
            addToBot(new ApplyPowerAction(AbstractDungeon.player, AbstractDungeon.player, power));
        }else{
            addToBot(new ApplyPowerAction(AbstractDungeon.player, AbstractDungeon.player, power,magicNumber));
        }
    }

    public void applyPowerSelf(AbstractPower power){
        applyPowerSelf(power,magicNumber);
    }

    public void applyPowerMonster(AbstractPower power,int magicNumber,AbstractMonster m){
        if(magicNumber==0){
            addToBot(new ApplyPowerAction(m, AbstractDungeon.player, power));
        }else{
            addToBot(new ApplyPowerAction(m, AbstractDungeon.player, power,magicNumber));
        }
    }

    public void applyPowerMonster(AbstractPower power,AbstractMonster m){
        applyPowerMonster(power,magicNumber,m);
    }

    public void setAttr(int baseDamage,int baseBlock,int baseMagicNumber){
        this.baseDamage = baseDamage;
        this.baseBlock = baseBlock;
        this.baseMagicNumber=baseMagicNumber;
    }

    public void setAttrUpg(int damageUpgrade,int blockUpgrade,int magicUpgrade){
        if(damageUpgrade!=0){
            this.upgradeDamage = true;
            this.damageUpgrade = damageUpgrade;
        }
        if(blockUpgrade!=0){
            this.upgradeBlock = true;
            this.blockUpgrade = blockUpgrade;
        }
        if(magicUpgrade!=0){
            this.upgradeMagic = true;
            this.magicUpgrade = magicUpgrade;
        }
    }

    public void setMagicNumberTwo(int magicNumberTwo,int magicNumberTwoUpgrade){
        this.magicNumberTwo = magicNumberTwo;
        this.magicNumberTwoUpgrade = magicNumberTwoUpgrade;
    }

    public void updateDescription() {

    }


    final private static Map<String, DynamicVariable> customVars = new HashMap<>();

    public static String makeID(String name) { return MutsumiMod.makeID(name); }
    public CardStrings cardStrings;

    public boolean upgradesDescription;

    public int baseCost;

    public boolean upgradeCost;
    public int costUpgrade;

    public boolean upgradeDamage;
    public boolean upgradeBlock;
    public boolean upgradeMagic;

    public int damageUpgrade;
    public int blockUpgrade;
    public int magicUpgrade;

    public boolean baseExhaust = false;
    public boolean upgExhaust = false;
    public boolean baseEthereal = false;
    public boolean upgEthereal = false;
    public boolean baseInnate = false;
    public boolean upgInnate = false;
    public boolean baseRetain = false;
    public boolean upgRetain = false;

    final public Map<String, LocalVarInfo> cardVariables = new HashMap<>();

    public BaseCaptainCard(String ID, CardStats info) {
        this(ID, info, getCardTextureString(removePrefix(ID), info.cardType));
    }
    public BaseCaptainCard(String ID, CardStats info, String cardImage) {
        this(ID, info.baseCost, info.cardType, info.cardTarget, info.cardRarity, info.cardColor, cardImage);
    }
    public BaseCaptainCard(String ID, int cost, CardType cardType, CardTarget target, CardRarity rarity, CardColor color) {
        this(ID, cost, cardType, target, rarity, color, getCardTextureString(removePrefix(ID), cardType));
    }
    public BaseCaptainCard(String ID, int cost, CardType cardType, CardTarget target, CardRarity rarity, CardColor color, String cardImage)
    {
        super(ID, getName(ID), cardImage, cost, getInitialDescription(ID), cardType, color, rarity, target);
        this.cardStrings = CardCrawlGame.languagePack.getCardStrings(cardID);
        this.originalName = cardStrings.NAME;

        this.baseCost = cost;

        this.upgradesDescription = cardStrings.UPGRADE_DESCRIPTION != null;
        this.upgradeCost = false;
        this.upgradeDamage = false;
        this.upgradeBlock = false;
        this.upgradeMagic = false;

        this.costUpgrade = cost;
        this.damageUpgrade = 0;
        this.blockUpgrade = 0;
        this.magicUpgrade = 0;
        updateDescription();
    }

    private static String getName(String ID) {
        return CardCrawlGame.languagePack.getCardStrings(ID).NAME;
    }
    private static String getInitialDescription(String ID) {
        return CardCrawlGame.languagePack.getCardStrings(ID).DESCRIPTION;
    }

    public final void setCustomVar(String key, int base) {
        this.setCustomVar(key, base, 0);
    }
    public final void setCustomVar(String key, int base, int upgrade) {
        setCustomVarValue(key, base, upgrade);

        if (!customVars.containsKey(key)) {
            QuickDynamicVariable var = new QuickDynamicVariable(key);
            customVars.put(key, var);
            BaseMod.addDynamicVariable(var);
            initializeDescription();
        }
    }

    public enum VariableType {
        DAMAGE,
        BLOCK,
        MAGIC
    }
    public final void setCustomVar(String key, VariableType type, int base) {
        setCustomVar(key, type, base, 0);
    }
    public final void setCustomVar(String key, VariableType type, int base, int upgrade) {
        setCustomVarValue(key, base, upgrade);

        switch (type) {
            case DAMAGE:
                calculateVarAsDamage(key);
                break;
            case BLOCK:
                calculateVarAsBlock(key);
                break;
        }

        if (!customVars.containsKey(key)) {
            QuickDynamicVariable var = new QuickDynamicVariable(key);
            customVars.put(key, var);
            BaseMod.addDynamicVariable(var);
            initializeDescription();
        }
    }
    public final void setCustomVar(String key, VariableType type, int base, TriFunction<BaseCaptainCard, AbstractMonster, Integer, Integer> preCalc) {
        setCustomVar(key, type, base, 0, preCalc);
    }
    public final void setCustomVar(String key, VariableType type, int base, int upgrade, TriFunction<BaseCaptainCard, AbstractMonster, Integer, Integer> preCalc) {
        setCustomVar(key, type, base, upgrade, preCalc, LocalVarInfo::noCalc);
    }
    public final void setCustomVar(String key, VariableType type, int base, TriFunction<BaseCaptainCard, AbstractMonster, Integer, Integer> preCalc, TriFunction<BaseCaptainCard, AbstractMonster, Integer, Integer> postCalc) {
        setCustomVar(key, type, base, 0, preCalc, postCalc);
    }
    public final void setCustomVar(String key, VariableType type, int base, int upgrade, TriFunction<BaseCaptainCard, AbstractMonster, Integer, Integer> preCalc, TriFunction<BaseCaptainCard, AbstractMonster, Integer, Integer> postCalc) {
        setCustomVarValue(key, base, upgrade);

        switch (type) {
            case DAMAGE:
                setVarCalculation(key, (c, m, baseVal)->{
                    boolean wasMultiDamage = c.isMultiDamage;
                    c.isMultiDamage = false;

                    int origBase = c.baseDamage, origVal = c.damage;

                    c.baseDamage = preCalc.apply(c, m, baseVal);

                    if (m != null)
                        c.calculateCardDamage(m);
                    else
                        c.applyPowers();

                    c.damage = postCalc.apply(c, m, c.damage);

                    c.baseDamage = origBase;
                    c.isMultiDamage = wasMultiDamage;

                    int result = c.damage;
                    c.damage = origVal;

                    return result;
                });
                break;
            case BLOCK:
                setVarCalculation(key, (c, m, baseVal)->{
                    int origBase = c.baseBlock, origVal = c.block;

                    c.baseBlock = preCalc.apply(c, m, baseVal);

                    if (m != null)
                        c.calculateCardDamage(m);
                    else
                        c.applyPowers();

                    c.block = postCalc.apply(c, m, c.block);

                    c.baseBlock = origBase;
                    int result = c.block;
                    c.block = origVal;
                    return result;
                });
                break;
            default:
                setVarCalculation(key, (c, m, baseVal)->{
                    int tmp = baseVal;

                    tmp = preCalc.apply(c, m, tmp);
                    tmp = postCalc.apply(c, m, tmp);

                    return tmp;
                });
                break;
        }

        if (!customVars.containsKey(key)) {
            QuickDynamicVariable var = new QuickDynamicVariable(key);
            customVars.put(key, var);
            BaseMod.addDynamicVariable(var);
            initializeDescription();
        }
    }

    private void setCustomVarValue(String key, int base, int upg) {
        cardVariables.compute(key, (k, old)->{
            if (old == null) {
                return new LocalVarInfo(base, upg);
            }
            else {
                old.base = base;
                old.upgrade = upg;
                return old;
            }
        });
    }

    public final void colorCustomVar(String key, Color normalColor) {
        colorCustomVar(key, normalColor, Settings.GREEN_TEXT_COLOR, Settings.RED_TEXT_COLOR, Settings.GREEN_TEXT_COLOR);
    }
    public final void colorCustomVar(String key, Color normalColor, Color increasedColor, Color decreasedColor) {
        colorCustomVar(key, normalColor, increasedColor, decreasedColor, increasedColor);
    }
    public final void colorCustomVar(String key, Color normalColor, Color increasedColor, Color decreasedColor, Color upgradedColor) {
        LocalVarInfo var = getCustomVar(key);
        if (var == null) {
            throw new IllegalArgumentException("Attempted to set color of variable that hasn't been registered.");
        }

        var.normalColor = normalColor;
        var.increasedColor = increasedColor;
        var.decreasedColor = decreasedColor;
        var.upgradedColor = upgradedColor;
    }


    private LocalVarInfo getCustomVar(String key) {
        return cardVariables.get(key);
    }

    public void calculateVarAsDamage(String key) {
        setVarCalculation(key, (c, m, base)->{
            boolean wasMultiDamage = c.isMultiDamage;
            c.isMultiDamage = false;

            int origBase = c.baseDamage, origVal = c.damage;

            c.baseDamage = base;
            if (m != null)
                c.calculateCardDamage(m);
            else
                c.applyPowers();

            c.baseDamage = origBase;
            c.isMultiDamage = wasMultiDamage;

            int result = c.damage;
            c.damage = origVal;

            return result;
        });
    }
    public void calculateVarAsBlock(String key) {
        setVarCalculation(key, (c, m, base)->{
            int origBase = c.baseBlock, origVal = c.block;

            c.baseBlock = base;
            if (m != null)
                c.calculateCardDamage(m);
            else
                c.applyPowers();

            c.baseBlock = origBase;
            int result = c.block;
            c.block = origVal;
            return result;
        });
    }
    public void setVarCalculation(String key, TriFunction<BaseCaptainCard, AbstractMonster, Integer, Integer> calculation) {
        cardVariables.get(key).calculation = calculation;
    }

    public int customVarBase(String key) {
        LocalVarInfo var = cardVariables.get(key);
        if (var == null)
            return -1;
        return var.base;
    }
    public int customVar(String key) {
        LocalVarInfo var = cardVariables == null ? null : cardVariables.get(key); //Prevents crashing when used with dynamic text
        if (var == null)
            return -1;
        return var.value;
    }
    public int[] customVarMulti(String key) {
        LocalVarInfo var = cardVariables.get(key);
        if (var == null)
            return null;
        return var.aoeValue;
    }
    public boolean isCustomVarModified(String key) {
        LocalVarInfo var = cardVariables.get(key);
        if (var == null)
            return false;
        return var.isModified();
    }
    public boolean customVarUpgraded(String key) {
        LocalVarInfo var = cardVariables.get(key);
        if (var == null)
            return false;
        return var.upgraded;
    }


    public final void setCostUpgrade(int costUpgrade)
    {
        this.costUpgrade = costUpgrade;
        this.upgradeCost = true;
    }
    public final void setExhaust(boolean exhaust) { this.setExhaust(exhaust, exhaust); }
    public final void setEthereal(boolean ethereal) { this.setEthereal(ethereal, ethereal); }
    public final void setInnate(boolean innate) {this.setInnate(innate, innate); }
    public final void setSelfRetain(boolean retain) {this.setSelfRetain(retain, retain); }
    public final void setExhaust(boolean baseExhaust, boolean upgExhaust)
    {
        this.baseExhaust = baseExhaust;
        this.upgExhaust = upgExhaust;
        this.exhaust = baseExhaust;
    }
    public final void setEthereal(boolean baseEthereal, boolean upgEthereal)
    {
        this.baseEthereal = baseEthereal;
        this.upgEthereal = upgEthereal;
        this.isEthereal = baseEthereal;
    }
    public void setInnate(boolean baseInnate, boolean upgInnate)
    {
        this.baseInnate = baseInnate;
        this.upgInnate = upgInnate;
        this.isInnate = baseInnate;
    }
    public void setSelfRetain(boolean baseRetain, boolean upgRetain)
    {
        this.baseRetain = baseRetain;
        this.upgRetain = upgRetain;
        this.selfRetain = baseRetain;
    }


    @Override
    public AbstractCard makeStatEquivalentCopy() {
        AbstractCard card = super.makeStatEquivalentCopy();

        if (card instanceof BaseCaptainCard)
        {
            card.rawDescription = this.rawDescription;
            ((BaseCaptainCard) card).upgradesDescription = this.upgradesDescription;

            ((BaseCaptainCard) card).baseCost = this.baseCost;

            ((BaseCaptainCard) card).upgradeCost = this.upgradeCost;
            ((BaseCaptainCard) card).upgradeDamage = this.upgradeDamage;
            ((BaseCaptainCard) card).upgradeBlock = this.upgradeBlock;
            ((BaseCaptainCard) card).upgradeMagic = this.upgradeMagic;

            ((BaseCaptainCard) card).costUpgrade = this.costUpgrade;
            ((BaseCaptainCard) card).damageUpgrade = this.damageUpgrade;
            ((BaseCaptainCard) card).blockUpgrade = this.blockUpgrade;
            ((BaseCaptainCard) card).magicUpgrade = this.magicUpgrade;

            ((BaseCaptainCard) card).baseExhaust = this.baseExhaust;
            ((BaseCaptainCard) card).upgExhaust = this.upgExhaust;
            ((BaseCaptainCard) card).baseEthereal = this.baseEthereal;
            ((BaseCaptainCard) card).upgEthereal = this.upgEthereal;
            ((BaseCaptainCard) card).baseInnate = this.baseInnate;
            ((BaseCaptainCard) card).upgInnate = this.upgInnate;
            ((BaseCaptainCard) card).baseRetain = this.baseRetain;
            ((BaseCaptainCard) card).upgRetain = this.upgRetain;

            //不用复制自定义动态字段
            for (Map.Entry<String, LocalVarInfo> varEntry : cardVariables.entrySet()) {
                LocalVarInfo target = ((BaseCaptainCard) card).getCustomVar(varEntry.getKey()),
                        current = varEntry.getValue();
                if (target == null) {
                    ((BaseCaptainCard) card).setCustomVar(varEntry.getKey(), current.base, current.upgrade);
                    target = ((BaseCaptainCard) card).getCustomVar(varEntry.getKey());
                }
                target.base = current.base;
                target.value = current.value;
                target.aoeValue = current.aoeValue;
                target.upgrade = current.upgrade;
                target.calculation = current.calculation;
            }
        }

        return card;
    }

    @Override
    public void upgrade()
    {
        if (!upgraded)
        {
            this.upgradeName();

            if (this.upgradesDescription)
            {
                if (cardStrings.UPGRADE_DESCRIPTION == null)
                {
                    MutsumiMod.logger.error("Card " + cardID + " upgrades description and has null upgrade description.");
                }
                else
                {
                    this.rawDescription = cardStrings.UPGRADE_DESCRIPTION;
                }
            }

            if (upgradeCost)
            {
                if (isCostModified && this.cost < this.baseCost && this.cost >= 0) {
                    int diff = this.costUpgrade - this.baseCost; //how the upgrade alters cost
                    this.upgradeBaseCost(this.cost + diff);
                    if (this.cost < 0)
                        this.cost = 0;
                }
                else {
                    upgradeBaseCost(costUpgrade);
                }
            }

            if (upgradeDamage)
                this.upgradeDamage(damageUpgrade);

            if (upgradeBlock)
                this.upgradeBlock(blockUpgrade);

            if (upgradeMagic)
                this.upgradeMagicNumber(magicUpgrade);

            magicNumberTwo+=magicNumberTwoUpgrade;

            for (LocalVarInfo var : cardVariables.values()) {
                if (var.upgrade != 0) {
                    var.base += var.upgrade;
                    var.value = var.base;
                    var.upgraded = true;
                }
            }

            if (baseExhaust ^ upgExhaust)
                this.exhaust = upgExhaust;

            if (baseInnate ^ upgInnate)
                this.isInnate = upgInnate;

            if (baseEthereal ^ upgEthereal)
                this.isEthereal = upgEthereal;

            if (baseRetain ^ upgRetain)
                this.selfRetain = upgRetain;

            updateDescription();
            //this.initializeDescription();
        }
    }

    boolean inCalc = false;
    @Override
    public void applyPowers() {
        if (!inCalc) {
            inCalc = true;
            for (LocalVarInfo var : cardVariables.values()) {
                var.value = var.calculation.apply(this, null, var.base);
            }
            if (isMultiDamage) {
                ArrayList<AbstractMonster> monsters = AbstractDungeon.getCurrRoom().monsters.monsters;
                AbstractMonster m;
                for (LocalVarInfo var : cardVariables.values()) {
                    if (var.aoeValue == null || var.aoeValue.length != monsters.size())
                        var.aoeValue = new int[monsters.size()];

                    for (int i = 0; i < monsters.size(); ++i) {
                        m = monsters.get(i);
                        var.aoeValue[i] = var.calculation.apply(this, m, var.base);
                    }
                }
            }
            inCalc = false;
        }

        super.applyPowers();
    }

    @Override
    public void calculateCardDamage(AbstractMonster m) {
        if (!inCalc) {
            inCalc = true;
            for (LocalVarInfo var : cardVariables.values()) {
                var.value = var.calculation.apply(this, m, var.base);
            }
            if (isMultiDamage) {
                ArrayList<AbstractMonster> monsters = AbstractDungeon.getCurrRoom().monsters.monsters;
                for (LocalVarInfo var : cardVariables.values()) {
                    if (var.aoeValue == null || var.aoeValue.length != monsters.size())
                        var.aoeValue = new int[monsters.size()];

                    for (int i = 0; i < monsters.size(); ++i) {
                        m = monsters.get(i);
                        var.aoeValue[i] = var.calculation.apply(this, m, var.base);
                    }
                }
            }
            inCalc = false;
        }

        super.calculateCardDamage(m);
    }

    @Override
    public void resetAttributes() {
        super.resetAttributes();

        for (LocalVarInfo var : cardVariables.values()) {
            var.value = var.base;
        }
    }

    private static class QuickDynamicVariable extends DynamicVariable {
        final String localKey, key;

        private BaseCaptainCard current = null;

        public QuickDynamicVariable(String key) {
            this.localKey = key;
            this.key = makeID(key);
        }

        @Override
        public String key() {
            return key;
        }

        @Override
        public void setIsModified(AbstractCard c, boolean v) {
            if (c instanceof BaseCaptainCard) {
                LocalVarInfo var = ((BaseCaptainCard) c).getCustomVar(localKey);
                if (var != null)
                    var.forceModified = v;
            }
        }

        @Override
        public boolean isModified(AbstractCard c) {
            return c instanceof BaseCaptainCard && (current = (BaseCaptainCard) c).isCustomVarModified(localKey);
        }

        @Override
        public int value(AbstractCard c) {
            return c instanceof BaseCaptainCard ? ((BaseCaptainCard) c).customVar(localKey) : 0;
        }

        @Override
        public int baseValue(AbstractCard c) {
            return c instanceof BaseCaptainCard ? ((BaseCaptainCard) c).customVarBase(localKey) : 0;
        }

        @Override
        public boolean upgraded(AbstractCard c) {
            return c instanceof BaseCaptainCard && ((BaseCaptainCard) c).customVarUpgraded(localKey);
        }

        public Color getNormalColor() {
            LocalVarInfo var;
            if (current == null || (var = current.getCustomVar(localKey)) == null)
                return Settings.CREAM_COLOR;

            return var.normalColor;
        }

        public Color getUpgradedColor() {
            LocalVarInfo var;
            if (current == null || (var = current.getCustomVar(localKey)) == null)
                return Settings.GREEN_TEXT_COLOR;

            return var.upgradedColor;
        }

        public Color getIncreasedValueColor() {
            LocalVarInfo var;
            if (current == null || (var = current.getCustomVar(localKey)) == null)
                return Settings.GREEN_TEXT_COLOR;

            return var.increasedColor;
        }

        public Color getDecreasedValueColor() {
            LocalVarInfo var;
            if (current == null || (var = current.getCustomVar(localKey)) == null)
                return Settings.RED_TEXT_COLOR;

            return var.decreasedColor;
        }
    }

    public static class LocalVarInfo {
        int base, value, upgrade;
        int[] aoeValue = null;
        boolean upgraded = false;
        boolean forceModified = false;
        Color normalColor = Settings.CREAM_COLOR;
        Color upgradedColor = Settings.GREEN_TEXT_COLOR;
        Color increasedColor = Settings.GREEN_TEXT_COLOR;
        Color decreasedColor = Settings.RED_TEXT_COLOR;

        TriFunction<BaseCaptainCard, AbstractMonster, Integer, Integer> calculation = LocalVarInfo::noCalc;

        public LocalVarInfo(int base, int upgrade) {
            this.base = this.value = base;
            this.upgrade = upgrade;
        }

        private static int noCalc(BaseCaptainCard c, AbstractMonster m, int base) {
            return base;
        }

        public boolean isModified() {
            return forceModified || base != value;
        }
    }
}